﻿using gt.rediscachemanager.Core.ClientServer;
using gt.rediscachemanager.Entry;
using gt.rediscachemanager.Handlers;
using gt.rediscachemanager.Utility;
using StackExchange.Redis;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace gt.rediscachemanager.Impl.RedisClient
{
    /// <summary>
    /// 支持主备节点切换模式
    /// </summary>
    public class SmartRedisClient : RedisClient
    {
        private SmartRedisClient m_backupClient;
        private SmartRedisClientConfiguration m_configuration;

        private MonitorHandler m_monitorHandler = null;
        private SyncHandler m_syncHandler = null;

        public SmartRedisClient(SmartRedisClientConfiguration configuration)
            : base(configuration)
        {
            this.m_configuration = configuration;
            if (configuration.BackupConfiguration != null &&
                (configuration.BackupConfiguration.Pool == null || string.IsNullOrEmpty(configuration.BackupConfiguration.Pool.Name)))
                throw new ArgumentNullException("clientName or pool");

            m_monitorHandler = new MonitorHandler(configuration.ClientName, configuration.DelayedRecoverySeconds, configuration.RedisFailoverWaitSeconds);

            if (configuration.AllowSwitch) EnableMonitor();
            if (configuration.AllowSync) EnableSync();
        }

        #region Handlers

        private void EnableMonitor()
        {
            m_monitorHandler.Monitor(m_clientServerPool);
            m_monitorHandler.Monitor_ConnectionRestoredEvent += M_monitorHandler_Monitor_ConnectionRestoredEvent;
            m_monitorHandler.Monitor_ConnectionFailedEvent += M_monitorHandler_Monitor_ConnectionFailedEvent;
        }
        private void EnableSync()
        {
            ActiveBackupClient();
            m_syncHandler = new SyncHandler();
        }

        #endregion

        #region Event

        private void M_monitorHandler_Monitor_ConnectionFailedEvent(object sender, MonitorConnectionFailedEventArgs e)
        {
            if (this.m_configuration.BackupConfiguration != null)
            {
                ActiveBackupClient();
                var msg = string.Format("redisclient:{0} cachepool:{1} redisnode:{2} connect failed,switch to backupCache:{3}.",
                   e.ClientName, e.CachePoolName, e.Node.ToString(), this.m_configuration.BackupConfiguration.Pool.Name);
                LogUtility.Warn(msg);
            }
        }

        private void M_monitorHandler_Monitor_ConnectionRestoredEvent(object sender, MonitorConnectionRestoredEventArgs e)
        {
            string msg = string.Format("redisclient:{0} cachepool:{1} redisnode:{2} restored,switch to mainCache:{3}.",
                e.ClientName, e.CachePoolName, e.Node.ToString(), e.CachePoolName);
            LogUtility.Warn(msg);
        }

        #endregion

        protected override TResult Execute<T, TResult>(RedisCommand command, RedisMessageWrapper<T, TResult> message, CommandFlags flags = CommandFlags.None)
        {
            var tempTuple = this.GetSmartClientServer(message.Key);
            var clientServer = tempTuple.Item1;
            var switched = tempTuple.Item2;
            var result = clientServer.ExecuteCommand(command, message, flags);
            //sync data to backup
            if (!switched && m_syncHandler != null)
            {
                m_syncHandler.SyncMessage(m_backupClient, command, message);
            }
            return result;
        }

        protected override async Task<TResult> ExecuteAsync<T, TResult>(RedisCommand command, RedisMessageWrapper<T, TResult> message, CommandFlags flags = CommandFlags.None)
        {
            var tempTuple = this.GetSmartClientServer(message.Key);
            var clientServer = tempTuple.Item1;
            var switched = tempTuple.Item2;
            return await clientServer.ExecuteCommandAsync(command, message, flags).ContinueWith((t) =>
            {
                //sync data to backup
                if (!switched && m_syncHandler != null)
                {
                    m_syncHandler.SyncMessage(m_backupClient, command, message);
                }
                return t.Result;
            }).ConfigureAwait(false);
        }

        public override Tuple<RedisClientServer, bool> GetSmartClientServer(string key)
        {
            var clientServer = base.GetClientServer(key);
            bool switched = false;      //是否发生切换
            if (!m_monitorHandler.CheckAvailable(clientServer) && this.m_backupClient != null)
            {
                switched = true;
                clientServer = this.m_backupClient.GetSmartClientServer(key).Item1;
            }
            return new Tuple<RedisClientServer, bool>(clientServer, switched);
        }

        public override RedisClientInfo RedisCacheInfo()
        {
            RedisClientInfo info = base.RedisCacheInfo();
            info.ConnectionCache.Nodes.ForEach(x => { x.Available = m_monitorHandler.CheckAvailable(GetClientServerByNodeName(x.Name)); });
            info.DelayedRecoverySeconds = this.m_configuration.DelayedRecoverySeconds ?? -1;
            info.RedisFailoverWaitSeconds = this.m_configuration.RedisFailoverWaitSeconds ?? 10;
            info.SupportSwitch = this.m_configuration.AllowSwitch;
            info.SupportSync = this.m_configuration.AllowSync;
            info.ConnectionCache.BackupConnectionCache = BuildBackupConnectCache(this.m_configuration.BackupConfiguration, CurrentBackup);
            return info;
        }

        public override IRedisClient BackupClient
        {
            get
            {
                if (this.m_backupClient == null)
                {
                    ActiveBackupClient();
                }
                return this.m_backupClient;
            }
        }

        #region private

        internal SmartRedisClient CurrentBackup
        {
            get { return this.m_backupClient; }
        }
        internal MonitorHandler Monitor { get { return m_monitorHandler; } }
        private RedisConnectCache BuildBackupConnectCache(SmartRedisClientConfiguration backupConfiguration, SmartRedisClient backupClient)
        {
            if (backupConfiguration == null) return null;
            RedisConnectCache entity = new RedisConnectCache();
            entity.Name = backupConfiguration.Pool.Name;
            entity.HasActive = backupClient != null;
            entity.Nodes = backupConfiguration.Pool.Nodes.Select((n) =>
              {
                  return new RedisConnectNode
                  {
                      Available = backupClient == null ? false : backupClient.Monitor.CheckAvailable(backupClient.GetClientServerByNodeName(n.Name)),
                      DB = n.DB,
                      Name = n.Name,
                      SlotFrom = n.SlotFrom,
                      SlotTo = n.SlotTo,
                      Master = n.Master,
                      Slaves = n.Slaves
                  };
              }).ToList();
            entity.BackupConnectionCache = BuildBackupConnectCache(backupConfiguration.BackupConfiguration, backupClient?.CurrentBackup);
            return entity;
        }

        private static object _obj = new object();
        private bool ActiveBackupClient()
        {
            bool flag = false;
            if (this.m_backupClient == null)
            {
                lock (_obj)
                {
                    if (this.m_backupClient == null)
                    {
                        if (this.m_configuration.BackupConfiguration != null)
                        {
                            this.m_backupClient = new SmartRedisClient(this.m_configuration.BackupConfiguration);

                            LogUtility.Warn(string.Format("redisclient {0} backup cachepool {1} create success.",
                                this.m_configuration.ClientName, this.m_configuration.BackupConfiguration.Pool.Name));
                            flag = true;
                        }
                    }
                }
            }
            return flag;
        }

        #endregion

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.m_backupClient != null)
                    this.m_backupClient.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}
